home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / apport / hookutils.py < prev    next >
Encoding:
Python Source  |  2009-09-25  |  13.9 KB  |  417 lines

  1. '''Convenience functions for use in package hooks.
  2.  
  3. Copyright (C) 2008-2009 Canonical Ltd.
  4. Author: Matt Zimmerman <mdz@canonical.com>
  5. Contributor: Brian Murray <brian@ubuntu.com>
  6.  
  7. This program is free software; you can redistribute it and/or modify it
  8. under the terms of the GNU General Public License as published by the
  9. Free Software Foundation; either version 2 of the License, or (at your
  10. option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
  11. the full text of the license.
  12. '''
  13.  
  14. import subprocess
  15. import hashlib
  16. import os
  17. import datetime
  18. import glob
  19. import re
  20. import string
  21.  
  22. import xml.dom, xml.dom.minidom
  23.  
  24. from packaging_impl import impl as packaging
  25.  
  26. _path_key_trans = string.maketrans('#/-_+','.....')
  27. def path_to_key(path):
  28.     '''Generate a valid report key name from a file path.
  29.         
  30.     This will meet apport's restrictions on the characters used in keys.
  31.     '''
  32.     return path.translate(_path_key_trans)
  33.  
  34. def attach_file_if_exists(report, path, key=None):
  35.     '''Attach file contents if file exists.'''
  36.  
  37.     if not key:
  38.         key = path_to_key(path)
  39.  
  40.     if os.path.exists(path):
  41.         attach_file(report, path, key)
  42.  
  43. def read_file(path):
  44.     '''Return the contents of the specified path. 
  45.         
  46.     Upon error, this will deliver a a text representation of the error,
  47.     instead of failing.
  48.     '''
  49.     try:
  50.         return open(path).read().strip()
  51.     except Exception, e:
  52.         return 'Error: ' + str(e)
  53.  
  54. def attach_file(report, path, key=None):
  55.     '''Attach a file to the report.
  56.  
  57.     If key is not specified, the key name will be derived from the file
  58.     name with path_to_key().
  59.     '''
  60.     if not key:
  61.         key = path_to_key(path)
  62.  
  63.     report[key] = read_file(path)
  64.  
  65. def attach_conffiles(report, package, conffiles=None):
  66.     '''Attach information about any modified or deleted conffiles'''
  67.  
  68.     output = command_output(['dpkg-query','-W','--showformat=${Conffiles}',
  69.                              package])
  70.     for line in output.split('\n'):
  71.         path, default_md5sum = line.strip().split()
  72.  
  73.         if conffiles and path not in conffiles: continue
  74.  
  75.         key = 'modified.conffile.' + path_to_key(path)
  76.  
  77.         if os.path.exists(path):
  78.             contents = open(path).read()
  79.             m = hashlib.md5()
  80.             m.update(contents)
  81.             calculated_md5sum = m.hexdigest()
  82.  
  83.             if calculated_md5sum != default_md5sum:
  84.                 report[key] = contents
  85.                 statinfo = os.stat(path)
  86.                 mtime = datetime.datetime.fromtimestamp(statinfo.st_mtime)
  87.                 mtime_key = 'mtime.conffile.' + path_to_key(path)
  88.                 report[mtime_key] = mtime.isoformat()
  89.         else:
  90.             report[key] = '[deleted]'
  91.  
  92. def attach_dmesg(report):
  93.     '''Attach information from the kernel ring buffer (dmesg).'''
  94.  
  95.     report['BootDmesg'] = open('/var/log/dmesg').read()
  96.     report['CurrentDmesg'] = command_output(['sh', '-c', 'dmesg | comm -13 /var/log/dmesg -'])
  97.  
  98. def attach_machinetype(report):
  99.     '''Calculate and attach a specific machine type if possible.'''
  100.  
  101.     if 'HalComputerInfo' in report:
  102.         system = ''
  103.         vendor = re.compile(r"system.hardware.vendor\s*=\s*'(.*)'\s*\(string\)")
  104.         match = vendor.search(report['HalComputerInfo'])
  105.         if match:
  106.             system += match.group(1).rstrip() + ' '
  107.         product = re.compile(r"system.hardware.product\s*=\s*'(.*)'\s*\(string\)")
  108.         match = product.search(report['HalComputerInfo'])
  109.         if match:
  110.             system += match.group(1).rstrip() + ' '
  111.  
  112.         if system != '':
  113.             report['MachineType'] = system.rstrip()
  114.  
  115. def attach_hardware(report):
  116.     attach_dmesg(report)
  117.  
  118.     attach_file(report, '/proc/interrupts', 'ProcInterrupts')
  119.     attach_file(report, '/proc/version_signature', 'ProcVersionSignature')
  120.     attach_file(report, '/proc/cpuinfo', 'ProcCpuinfo')
  121.     attach_file(report, '/proc/cmdline', 'ProcCmdLine')
  122.     attach_file(report, '/proc/modules', 'ProcModules')
  123.  
  124.     report['Lspci'] = command_output(['lspci','-vvnn'])
  125.     report['Lsusb'] = command_output(['lsusb'])
  126.     report['HalComputerInfo'] = hal_dump_udi('/org/freedesktop/Hal/devices/computer')
  127.  
  128.     if 'Uname' in report:
  129.         # already covered in ProcVersionSignature
  130.         del report['Uname']
  131.  
  132.     # Use the hardware information to create a machine type.
  133.     attach_machinetype(report)
  134.  
  135. def attach_alsa(report):
  136.     '''Attach ALSA subsystem information to the report.
  137.  
  138.     (loosely based on http://www.alsa-project.org/alsa-info.sh)
  139.         '''
  140.     attach_file_if_exists(report, os.path.expanduser('~/.asoundrc'),
  141.                           'UserAsoundrc')
  142.     attach_file_if_exists(report, os.path.expanduser('~/.asoundrc.asoundconf'),
  143.                           'UserAsoundrcAsoundconf')
  144.     attach_file_if_exists(report, '/etc/asound.conf')
  145.  
  146.     report['AlsaDevices'] = command_output(['ls','-l','/dev/snd/'])
  147.     report['AplayDevices'] = command_output(['aplay','-l'])
  148.     report['ArecordDevices'] = command_output(['arecord','-l'])
  149.  
  150.     report['PciMultimedia'] = pci_devices(PCI_MULTIMEDIA)
  151.  
  152.     cards = []
  153.     for line in open('/proc/asound/cards'):
  154.         if ']:' in line:
  155.             fields = line.lstrip().split()
  156.             cards.append(int(fields[0]))
  157.  
  158.     for card in cards:
  159.         key = 'Card%d.Amixer.info' % card
  160.         report[key] = command_output(['amixer', '-c', str(card), 'info'])
  161.         key = 'Card%d.Amixer.values' % card
  162.         report[key] = command_output(['amixer', '-c', str(card)])
  163.  
  164.         for codecpath in glob.glob('/proc/asound/card%d/codec*' % card):
  165.             if os.path.isfile(codecpath):
  166.                 codec = os.path.basename(codecpath)
  167.                 key = 'Card%d.Codecs.%s' % (card, path_to_key(codec))
  168.                 attach_file(report, codecpath, key=key)
  169.             elif os.path.isdir(codecpath):
  170.                 codec = os.path.basename(codecpath)
  171.                 for name in os.listdir(codecpath):
  172.                     path = os.path.join(codecpath, name)
  173.                     key = 'Card%d.Codecs.%s.%s' % (card, path_to_key(codec), path_to_key(name))
  174.                     attach_file(report, path, key)
  175.  
  176.     report['AudioDevicesInUse'] = command_output(
  177.         ['fuser','-v'] + glob.glob('/dev/dsp*') 
  178.             + glob.glob('/dev/snd/*')
  179.             + glob.glob('/dev/seq*') )
  180.  
  181.     attach_dmesg(report)
  182.  
  183.     # This seems redundant with the amixer info, do we need it?
  184.     #report['AlsactlStore'] = command-output(['alsactl', '-f', '-', 'store'])
  185.  
  186. def command_output(command, input = None, stderr = subprocess.STDOUT):
  187.     '''Try to execute given command (array) and return its stdout. 
  188.     
  189.     In case of failure, a textual error gets returned.
  190.     '''
  191.     try:
  192.        sp = subprocess.Popen(command, stdout=subprocess.PIPE,
  193.                              stderr=stderr, close_fds=True)
  194.     except OSError, e:
  195.        return 'Error: ' + str(e)
  196.  
  197.     out = sp.communicate(input)[0]
  198.     if sp.returncode == 0:
  199.        return out.strip()
  200.     else:
  201.        return 'Error: command %s failed with exit code %i: %s' % (
  202.            str(command), sp.returncode, out)
  203.  
  204. def recent_syslog(pattern):
  205.     '''Extract recent messages from syslog which match a regex.
  206.         
  207.     pattern should be a "re" object.
  208.     '''
  209.     lines = ''
  210.     for line in open('/var/log/syslog'):
  211.         if pattern.search(line):
  212.             lines += line
  213.     return lines
  214.  
  215. PCI_MASS_STORAGE = 0x01
  216. PCI_NETWORK = 0x02
  217. PCI_DISPLAY = 0x03
  218. PCI_MULTIMEDIA = 0x04
  219. PCI_MEMORY = 0x05
  220. PCI_BRIDGE = 0x06
  221. PCI_SIMPLE_COMMUNICATIONS = 0x07
  222. PCI_BASE_SYSTEM_PERIPHERALS = 0x08
  223. PCI_INPUT_DEVICES = 0x09
  224. PCI_DOCKING_STATIONS = 0x0a
  225. PCI_PROCESSORS = 0x0b
  226. PCI_SERIAL_BUS = 0x0c
  227.  
  228. def pci_devices(*pci_classes):
  229.     '''Return a text dump of PCI devices attached to the system.'''
  230.  
  231.     if not pci_classes:
  232.         return command_output(['lspci', '-vvnn'])
  233.  
  234.     slots = []
  235.     output = command_output(['lspci','-vvmmnn'])
  236.     for paragraph in output.split('\n\n'):
  237.         pci_class = None
  238.         pci_subclass = None
  239.         slot = None
  240.  
  241.         for line in paragraph.split('\n'):
  242.             key, value = line.split(':',1)
  243.             value = value.strip()
  244.             key = key.strip()
  245.             if key == 'Class':
  246.                 n = int(value[-5:-1],16)
  247.                 pci_class = (n & 0xff00) >> 8
  248.                 pci_subclass = (n & 0x00ff)
  249.             elif key == 'Slot':
  250.                 slot = value
  251.  
  252.         if pci_class and slot and pci_class in pci_classes:
  253.             slots.append(slot)
  254.  
  255.     cmd = ['lspci','-vvnn']
  256.     for slot in slots:
  257.         cmd.extend(['-s',slot])
  258.  
  259.     return command_output(cmd)
  260.  
  261. def usb_devices():
  262.     '''Return a text dump of USB devices attached to the system.'''
  263.  
  264.     # TODO: would be nice to be able to filter by interface class
  265.     return command_output(['lsusb','-v'])
  266.  
  267. def hal_find_by_capability(capability):
  268.     '''Retrieve a list of UDIs for hal objects having the specified capability.'''
  269.  
  270.     output = command_output(['hal-find-by-capability',
  271.                              '--capability',capability])
  272.     return output.split('\n')
  273.  
  274. def hal_dump_udi(udi):
  275.     '''Dump the properties of a HAL object, specified by its UDI.'''
  276.  
  277.     out = command_output(['lshal','-u',udi])
  278.  
  279.     # filter out serial numbers
  280.     result = ''
  281.     for l in out.splitlines():
  282.         if '.serial =' in l:
  283.             continue
  284.         result += l + '\n'
  285.  
  286.     return result
  287.  
  288. def files_in_package(package, globpat=None):
  289.     '''Retrieve a list of files owned by package, optionally matching globpat'''
  290.  
  291.     files = packaging.get_files(package)
  292.     if globpat:
  293.         result = [f for f in files if glob.fnmatch.fnmatch(f, globpat)]
  294.     else:
  295.         result = files
  296.     return result
  297.  
  298. def attach_gconf(report, package):
  299.     '''Attach information about gconf keys set to non-default values.'''
  300.  
  301.     import gconf
  302.     import glib
  303.  
  304.     client = gconf.client_get_default()
  305.  
  306.     non_defaults = {}
  307.     for schema_file in files_in_package(package,
  308.                                     '/usr/share/gconf/schemas/*.schemas'):
  309.  
  310.         for key, default_value in _parse_gconf_schema(schema_file).items():
  311.             try:
  312.                 value = client.get(key).to_string()
  313.                 if value != default_value:
  314.                     non_defaults[key] = value
  315.             except glib.GError:
  316.                 # Fall back to gconftool-2 and string comparison
  317.                 value = command_output(['gconftool-2','-g',key])
  318.  
  319.                 if value != default_value:
  320.                     non_defaults[key] = value
  321.  
  322.     if non_defaults:
  323.         s = ''
  324.         keys = non_defaults.keys()
  325.         keys.sort()
  326.         for key in keys:
  327.             value = non_defaults[key]
  328.             s += '%s=%s\n' % (key, value)
  329.  
  330.         report['GConfNonDefault'] = s
  331.  
  332. def attach_network(report):
  333.     '''Attach network-related information to report.'''
  334.  
  335.     report['IpRoute'] = command_output(['ip','route'])
  336.     report['IpAddr'] = command_output(['ip','addr'])
  337.     report['PciNetwork'] = pci_devices(PCI_NETWORK)
  338.  
  339.     for var in ('http_proxy', 'ftp_proxy', 'no_proxy'):
  340.         if var in os.environ:
  341.             report[var] = os.environ[var]
  342.  
  343. def attach_printing(report):
  344.     '''Attach printing information to the report.
  345.  
  346.     Based on http://wiki.ubuntu.com/PrintingBugInfoScript.
  347.     '''
  348.     attach_file_if_exists(report, '/etc/papersize', 'Papersize')
  349.     attach_file_if_exists(report, '/var/log/cups/error_log', 'CupsErrorLog')
  350.     report['Locale'] = command_output(['locale'])
  351.     report['Lpstat'] = command_output(['lpstat', '-v'])
  352.  
  353.     ppds = glob.glob('/etc/cups/ppd/*.ppd')
  354.     if ppds:
  355.         nicknames = command_output(['fgrep', '-H', '*NickName'] + ppds)
  356.         report['PpdFiles'] = re.sub('/etc/cups/ppd/(.*).ppd:\*NickName: *"(.*)"', '\g<1>: \g<2>', nicknames)
  357.  
  358.     report['PrintingPackages'] = package_versions(
  359.         'foo2zjs', 'foomatic-db', 'foomatic-db-engine',
  360.         'foomatic-db-gutenprint', 'foomatic-db-hpijs', 'foomatic-filters',
  361.         'foomatic-gui', 'hpijs', 'hplip', 'm2300w', 'min12xxw', 'c2050',
  362.         'hpoj', 'pxljr', 'pnm2ppa', 'splix', 'hp-ppd', 'hpijs-ppds',
  363.         'linuxprinting.org-ppds', 'openprinting-ppds',
  364.         'openprinting-ppds-extra', 'ghostscript', 'cups',
  365.         'cups-driver-gutenprint', 'foomatic-db-gutenprint', 'ijsgutenprint',
  366.         'cupsys-driver-gutenprint', 'gimp-gutenprint', 'gutenprint-doc',
  367.         'gutenprint-locales', 'system-config-printer-common', 'kdeprint')
  368.  
  369. def attach_related_packages(report, packages):
  370.     '''Attach version information for related packages
  371.  
  372.        In the future, this might also run their hooks.'''
  373.     report['RelatedPackageVersions'] = package_versions(*packages)
  374.  
  375. def package_versions(*packages):
  376.     '''Return a text listing of package names and versions for the specified
  377.     packages.  Arguments may be package names or glob patterns, e.g. "foo*"'''
  378.  
  379.     versions = ''
  380.     for package_pattern in packages:
  381.         for package in package_glob(package_pattern):
  382.             try:
  383.                 version = packaging.get_version(package)
  384.             except ValueError:
  385.                 version = 'N/A'
  386.             if version is None:
  387.                 version = 'N/A'
  388.             versions += '%s %s\n' % (package, version)
  389.  
  390.     return versions
  391.  
  392. def package_glob(name):
  393.     '''Return a list of known packages matching name'''
  394.  
  395.     all_packages = command_output(['apt-cache', 'pkgnames']).split('\n')
  396.     return glob.fnmatch.filter(all_packages, name)
  397.  
  398. def _parse_gconf_schema(schema_file):
  399.     ret = {}
  400.  
  401.     dom = xml.dom.minidom.parse(schema_file)
  402.     for gconfschemafile in dom.getElementsByTagName('gconfschemafile'):
  403.         for schemalist in gconfschemafile.getElementsByTagName('schemalist'):
  404.             for schema in schemalist.getElementsByTagName('schema'):
  405.                 key = schema.getElementsByTagName('applyto')[0].childNodes[0].data
  406.                 type = schema.getElementsByTagName('type')[0].childNodes[0].data
  407.                 default = schema.getElementsByTagName('default')[0].childNodes[0].data
  408.                 if type == 'bool':
  409.                     if default:
  410.                         ret[key] = 'true'
  411.                     else:
  412.                         ret[key] = 'false'
  413.                 else:
  414.                     ret[key] = default
  415.  
  416.     return ret
  417.